﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System.Runtime.Serialization;
using System;
using System.Collections.Generic;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
#if USE_HYPER
using Hyper.ComponentModel;
#endif

namespace BrainTechLLC.ThreadSafeObjects
{
    /// <summary>
    /// A simple queue that avoids using lock().  Fast/efficient to dequeue/enqueue from multiple threads
    /// </summary>	
#if NO_SILVERLIGHT
    [TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
#endif
    [Serializable]
    [DataContract]
    public class ThreadSafeQueue<TItem> : Lockable, ICollection, INotifyCollectionChanged,
        IMultipleItems<TItem>, ISupportsCount, ISupportsEnqueueDequeue<TItem> where TItem : class
    {
        public override string ToString()
        {
            return _count.ToString();
        }

        [DataMember]
        public Queue<TItem> _queue = new Queue<TItem>();

        [DataMember]
        public int _count;

        public List<TItem> AllItems { get { return new List<TItem>(ArrayOfItems); } }

        public int Count { get { return _count; } }

        /// <summary>
        /// Enqueues an item and returns the new count of items in the queue
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public int Enqueue(TItem item)
        {
            // We need to be sure that no other threads simultaneously modify the shared _queue
            // object during our enqueue operation
            AquireLock();
            {
                _queue.Enqueue(item);                
                _count++;
            }
            ReleaseLock();

            OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);
            return _count;
        }

        /// <summary>
        /// Enqueues if a lock can be aquired, otherwise does nothing
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public int EnqueueIfPossible(TItem item)
        {
            // We need to be sure that no other threads simultaneously modify the shared _queue
            // object during our enqueue operation
            if (TryAquireLock())
            {
                _queue.Enqueue(item);
                _count++;
                ReleaseLock();

                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);
            }            
            
            return _count;
        }

        /// <summary>
        /// Enqueues multiple items and returns the new count of items in the queue
        /// </summary>
        /// <param name="items"></param>
        public int EnqueueMultiple(List<TItem> items)
        {
            AquireLock();
            {
                foreach (TItem item in items)
                    _queue.Enqueue(item);

                _count += items.Count;
            }
            ReleaseLock();

            OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, items);

            return _count;
        }

        /// <summary>
        /// Dequeues multiple items (up to maxItems), places them in list 'items'
        /// Returns the number of items actually dequeued
        /// </summary>
        /// <param name="items"></param>
        /// <param name="maxItems"></param>
        /// <returns></returns>
        public int DequeueMultiple(List<TItem> items, int maxItems)
        {
            int dequeued = 0;

            AquireLock();
            {
                while (_count > 0 && dequeued < maxItems)
                {
                    TItem item = _queue.Dequeue();
                    items.Add(item);
                    _count--;
                    dequeued++;
                }
            }
            ReleaseLock();

            if (CollectionChanged != null)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, items);

            return dequeued;
        }

        /// <summary>
        /// Dequeues multiple items (up to maxItems), places them in array 'items'
        /// Returns the number of items actually dequeued
        /// </summary>
        /// <param name="items"></param>
        /// <param name="maxItems"></param>
        /// <returns></returns>
        public int DequeueMultiple(TItem[] items, int maxItems)
        {
            int dequeued = 0;

            AquireLock();
            {
                while (_count > 0 && dequeued < maxItems)
                {
                    TItem item = _queue.Dequeue();
                    items[dequeued] = item;
                    dequeued++;
                    _count--;
                }
            }
            ReleaseLock();

            if (CollectionChanged != null)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, new List<TItem>(items));

            return dequeued;
        }

        /// <summary>
        /// Dequeues an item, or returns null if there are no items in the queue
        /// </summary>
        /// <returns></returns>
        public TItem DequeueItem()
        {
            TItem found;
            if (Dequeue(out found))
            {
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, found);
                return found;
            }
            return null;
        }

        /// <summary>
        /// Dequeues an item, returns false if the queue is empty, otherwise true if an item was successfully dequeued
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Dequeue(out TItem item)
        {
            item = null;

            // We need to be sure that no other threads simultaneously modify the shared _queue
            // object during our dequeue operation
            AquireLock();
            {
                if (_count > 0)
                {
                    item = _queue.Dequeue();
                    _count--;
                }
            }
            ReleaseLock();

            if (item != null)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, item);

            return (item != null);
        }

        /// <summary>
        /// Determines if an item is in the queue
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool IsInList(TItem item)
        {
            bool found;

            AquireLock();
            {
                found = _queue.Contains(item);
            }
            ReleaseLock();

            return found;
        }

        /// <summary>
        /// Determines if an item is in the queue
        /// </summary>
        /// <param name="match"></param>
        /// <returns></returns>
        public bool IsInList(Func<TItem, bool> match)
        {
            bool found = false;

            var list = AllItems;

            AquireLock();
            {                
                for (int n = 0; n < list.Count; n++)
                {
                    if (match(list[n]))
                    {
                        found = true;
                        break;
                    }
                }
            }
            ReleaseLock();

            return found;
        }

        /// <summary>
        /// Returns an array of all items in the queue
        /// </summary>
        public TItem[] ArrayOfItems
        {
            get
            {
                TItem[] list;

                AquireLock();
                {
                    list = _queue.ToArray();
                }
                ReleaseLock();

                return list;
            }
        }

        /// <summary>
        /// Clears the queue
        /// </summary>
        public virtual void Clear()
        {
            IList<TItem> items = null;

            if (CollectionChanged != null)
                items = new List<TItem>(AllItems);

            AquireLock();
            {
                _queue.Clear();
                _count = 0;
            }
            ReleaseLock();

            OnNotifyCollectionChanged(NotifyCollectionChangedAction.Reset, items);
        }

        public void OnNotifyCollectionChanged(NotifyCollectionChangedAction action, IList<TItem> changedItems)
        {
#if SILVERLIGHT
#else
            if (CollectionChanged != null)
            {
                switch (action)
                {
                    case NotifyCollectionChangedAction.Add:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action, changedItems));
                        break;
                    case NotifyCollectionChangedAction.Remove:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action, changedItems));
                        break;
                    case NotifyCollectionChangedAction.Replace:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action, changedItems[0], changedItems[1]));
                        break;
                    case NotifyCollectionChangedAction.Reset:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action));
                        break;
                }
            }
#endif
        }

        public void OnNotifyCollectionChanged(NotifyCollectionChangedAction action, TItem changedItem)
        {
            if (CollectionChanged != null)
                OnNotifyCollectionChanged(action, new List<TItem>() { changedItem });
        }

        #region INotifyCollectionChanged Members

        [field: NonSerialized]
        public event NotifyCollectionChangedEventHandler CollectionChanged;

        #endregion

        #region ICollection Members

        public void CopyTo(Array array, int index)
        {
            _queue.CopyTo((TItem[])array, index);
        }

        public bool IsSynchronized
        {
            get { return true; }
        }

        public object SyncRoot
        {
            get { return this; }
        }

        #endregion

        #region IEnumerable Members

        public IEnumerator GetEnumerator()
        {
            return _queue.GetEnumerator();
        }

        #endregion
    }

    /// <summary>
    /// A simple Stack that avoids using lock().  Fast/efficient to push/pop from multiple threads
    /// </summary>	
#if NO_SILVERLIGHT
    [TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
#endif
    [Serializable]
    [DataContract]
    public class ThreadSafeStack<TItem> : Lockable, ICollection, INotifyCollectionChanged,
        IMultipleItems<TItem>, ISupportsCount where TItem : class
    {
        public override string ToString()
        {
            return _count.ToString();
        }

        [DataMember]
        public Stack<TItem> _stack = new Stack<TItem>();

        [DataMember]
        public int _count;

        public List<TItem> AllItems { get { return new List<TItem>(ArrayOfItems); } }
        public int Count { get { return _count; } }

        /// <summary>
        /// Pushes an item and returns the new count of items on the stack
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public int Push(TItem item)
        {
            // We need to be sure that no other threads simultaneously modify the shared _Stack
            // object during our Push operation
            AquireLock();
            {
                _stack.Push(item);
                _count++;
            }
            ReleaseLock();

            OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);
            return _count;
        }

        /// <summary>
        /// Pushes if a lock can be aquired, otherwise does nothing
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public int PushIfPossible(TItem item)
        {
            // We need to be sure that no other threads simultaneously modify the shared _queue
            // object during our push operation
            if (TryAquireLock())
            {
                _stack.Push(item);
                _count++;
                ReleaseLock();

                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);
            }

            return _count;
        }


        /// <summary>
        /// Pushes multiple items and returns the new count of items on the stack
        /// </summary>
        /// <param name="items"></param>
        public int PushMultiple(List<TItem> items)
        {
            AquireLock();
            {
                foreach (TItem item in items)
                    _stack.Push(item);

                _count += items.Count;
            }
            ReleaseLock();

            OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, items);
            return _count;
        }

        /// <summary>        
        /// Pops multiple items (up to maxItems), places them in list 'items'
        /// Returns the number of items actually popped        
        /// </summary>
        /// <param name="items"></param>
        /// <param name="maxItems"></param>
        /// <returns></returns>
        public int PopMultiple(List<TItem> items, int maxItems)
        {
            int popped = 0;

            AquireLock();
            {
                while (_count > 0 && popped < maxItems)
                {
                    TItem item = _stack.Pop();
                    items.Add(item);
                    _count--;
                    popped++;
                }
            }
            ReleaseLock();

            if (CollectionChanged != null)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, items);

            return popped;
        }

        /// <summary>
        /// Pops multiple items (up to maxItems), places them in array 'items'
        /// Returns the number of items actually popped
        /// </summary>
        /// <param name="items"></param>
        /// <param name="maxItems"></param>
        /// <returns></returns>
        public int PopMultiple(TItem[] items, int maxItems)
        {
            int popped = 0;

            AquireLock();
            {
                while (_count > 0 && popped < maxItems)
                {
                    TItem item = _stack.Pop();
                    items[popped] = item;
                    popped++;
                    _count--;
                }
            }
            ReleaseLock();

            if (CollectionChanged != null)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, new List<TItem>(items));

            return popped;
        }

        /// <summary>
        /// Pops an item from the stack.  Returns null if stack was empty
        /// </summary>
        /// <returns></returns>
        public TItem PopItem()
        {
            TItem found;
            if (Pop(out found))
            {
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, found);
                return found;
            }
            return null;
        }

        /// <summary>
        /// Pops an item from the stack.  Returns false if stack was empty, returns true if item was successfully popped
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Pop(out TItem item)
        {
            item = null;

            // We need to be sure that no other threads simultaneously modify the shared _Stack
            // object during our popped operation
            AquireLock();
            {
                if (_count > 0)
                {
                    item = _stack.Pop();
                    _count--;
                }
            }
            ReleaseLock();

            if (item != null)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, item);

            return (item != null);
        }

        /// <summary>
        /// Peeks at the top item on the stack.  Returns null if stack is empty
        /// </summary>
        /// <returns></returns>
        public TItem PeekTop()
        {
            TItem item;

            if (_count > 0)
            {
                AquireLock();
                {
                    if (_count > 0)
                    {
                        item = _stack.Peek();
                    }
                    else
                    {
                        item = null;
                    }
                }
                ReleaseLock();
            }
            else
            {
                item = null;
            }

            return item;
        }

        /// <summary>
        /// Determines if an item is in the stack
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool IsInList(TItem item)
        {
            bool found;

            AquireLock();
            {
                found = _stack.Contains(item);
            }
            ReleaseLock();

            return found;
        }

        /// <summary>
        /// Returns an array of all items in the stack
        /// </summary>
        public TItem[] ArrayOfItems
        {
            get
            {
                TItem[] list;

                AquireLock();
                {
                    list = _stack.ToArray();
                }
                ReleaseLock();

                return list;
            }
        }

        /// <summary>
        /// Clears the stack
        /// </summary>
        public void Clear()
        {
            IList<TItem> items = null;

            if (CollectionChanged != null)
                items = new List<TItem>(AllItems);

            AquireLock();
            {
                _stack.Clear();
                _count = 0;
            }
            ReleaseLock();

            OnNotifyCollectionChanged(NotifyCollectionChangedAction.Reset, items);
        }

        public void OnNotifyCollectionChanged(NotifyCollectionChangedAction action, IList<TItem> changedItems)
        {
#if SILVERLIGHT
#else
            if (CollectionChanged != null)
            {
                switch (action)
                {
                    case NotifyCollectionChangedAction.Add:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action, changedItems));
                        break;
                    case NotifyCollectionChangedAction.Remove:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action, changedItems));
                        break;
                    case NotifyCollectionChangedAction.Replace:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action, changedItems[0], changedItems[1]));
                        break;
                    case NotifyCollectionChangedAction.Reset:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action));
                        break;
                }
            }
#endif
        }

        public void OnNotifyCollectionChanged(NotifyCollectionChangedAction action, TItem changedItem)
        {
            if (CollectionChanged != null)
                OnNotifyCollectionChanged(action, new List<TItem>() { changedItem });
        }

        #region INotifyCollectionChanged Members

        [field: NonSerialized]
        public event NotifyCollectionChangedEventHandler CollectionChanged;

        #endregion

        #region ICollection Members

        public void CopyTo(Array array, int index)
        {
            _stack.CopyTo((TItem[])array, index);
        }

        public bool IsSynchronized
        {
            get { return true; }
        }

        public object SyncRoot
        {
            get { return this; }
        }

        #endregion

        #region IEnumerable Members

        public IEnumerator GetEnumerator()
        {
            return _stack.GetEnumerator();
        }

        #endregion
    }

    /// <summary>
    /// A simple Stack that avoids using lock().  Fast/efficient to push/pop from multiple threads
    /// </summary>	
#if NO_SILVERLIGHT
    [TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
#endif
    [Serializable]
    [DataContract]
    public class ThreadSafeStackNonRef<TItem> : Lockable, ICollection, INotifyCollectionChanged,
        IMultipleItems<TItem>, ISupportsCount
    {
        public override string ToString()
        {
            return _count.ToString();
        }

        [DataMember]
        public Stack<TItem> _stack = new Stack<TItem>();

        [DataMember]
        public int _count;

        public List<TItem> AllItems { get { return new List<TItem>(ArrayOfItems); } }
        public int Count { get { return _count; } }

        /// <summary>
        /// Pushes an item and returns the new count of items on the stack
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public int Push(TItem item)
        {
            // We need to be sure that no other threads simultaneously modify the shared _Stack
            // object during our Push operation
            AquireLock();
            {
                _stack.Push(item);
                _count++;
            }
            ReleaseLock();

            OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);
            return _count;
        }

        public void PushMultiple(List<TItem> items)
        {
            AquireLock();
            {
                foreach (TItem item in items)
                    _stack.Push(item);

                _count += items.Count;
            }
            ReleaseLock();

            OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, items);
        }

        /// <summary>        
        /// Pops multiple items (up to maxItems), places them in list 'items'
        /// Returns the number of items actually popped        
        /// </summary>
        /// <param name="items"></param>
        /// <param name="maxItems"></param>
        /// <returns></returns>
        public int PopMultiple(List<TItem> items, int maxItems)
        {
            int popped = 0;

            AquireLock();
            {
                while (_count > 0 && popped < maxItems)
                {
                    TItem item = _stack.Pop();
                    items.Add(item);
                    _count--;
                    popped++;
                }
            }
            ReleaseLock();

            if (CollectionChanged != null)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, items);

            return popped;
        }

        /// <summary>
        /// Pops multiple items (up to maxItems), places them in array 'items'
        /// Returns the number of items actually popped
        /// </summary>
        /// <param name="items"></param>
        /// <param name="maxItems"></param>
        /// <returns></returns>
        public int PopMultiple(TItem[] items, int maxItems)
        {
            int popped = 0;

            AquireLock();
            {
                while (_count > 0 && popped < maxItems)
                {
                    TItem item = _stack.Pop();
                    items[popped] = item;
                    popped++;
                    _count--;
                }
            }
            ReleaseLock();

            if (CollectionChanged != null)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, new List<TItem>(items));

            return popped;
        }

        /// <summary>
        /// Peeks at the top item on the stack.  Returns null if stack is empty
        /// </summary>
        /// <returns></returns>
        public TItem PeekTop()
        {
            TItem item;

            if (_count > 0)
            {
                AquireLock();
                {
                    if (_count > 0)
                    {
                        item = _stack.Peek();
                    }
                    else
                    {
                        item = default(TItem);
                    }
                }
                ReleaseLock();
            }
            else
            {
                item = default(TItem);
            }

            return item;
        }

        /// <summary>
        /// Pops an item from the stack.  Returns null if stack was empty
        /// </summary>
        /// <returns></returns>
        public TItem PopItem()
        {
            TItem found;
            if (Pop(out found))
            {
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, found);
                return found;
            }
            return default(TItem);
        }

        /// <summary>
        /// Pops an item from the stack.  Returns false if stack was empty, returns true if item was successfully popped
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Pop(out TItem item)
        {
            bool gotItem = false;

            // We need to be sure that no other threads simultaneously modify the shared _Stack
            // object during our popped operation
            AquireLock();
            {
                if (_count > 0)
                {
                    item = _stack.Pop();
                    _count--;
                    gotItem = true;
                }
                else
                {
                    item = default(TItem);
                }
            }
            ReleaseLock();

            if (gotItem)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, item);

            return gotItem;
        }

        /// <summary>
        /// Determines if an item is in the stack
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool IsInList(TItem item)
        {
            bool found;

            AquireLock();
            {
                found = _stack.Contains(item);
            }
            ReleaseLock();

            return found;
        }

        /// <summary>
        /// Returns an array of all items in the stack
        /// </summary>
        public TItem[] ArrayOfItems
        {
            get
            {
                TItem[] list;

                AquireLock();
                {
                    list = _stack.ToArray();
                }
                ReleaseLock();

                return list;
            }
        }

        /// <summary>
        /// Clears the stack
        /// </summary>
        public virtual void Clear()
        {
            IList<TItem> items = null;

            if (CollectionChanged != null)
                items = new List<TItem>(AllItems);

            AquireLock();
            {
                _stack.Clear();
                _count = 0;
            }
            ReleaseLock();

            OnNotifyCollectionChanged(NotifyCollectionChangedAction.Reset, items);
        }

        public void OnNotifyCollectionChanged(NotifyCollectionChangedAction action, IList<TItem> changedItems)
        {
#if SILVERLIGHT
#else
            if (CollectionChanged != null)
            {
                switch (action)
                {
                    case NotifyCollectionChangedAction.Add:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action, changedItems));
                        break;
                    case NotifyCollectionChangedAction.Remove:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action, changedItems));
                        break;
                    case NotifyCollectionChangedAction.Replace:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action, changedItems[0], changedItems[1]));
                        break;
                    case NotifyCollectionChangedAction.Reset:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action));
                        break;
                }
            }
#endif
        }

        public void OnNotifyCollectionChanged(NotifyCollectionChangedAction action, TItem changedItem)
        {
            if (CollectionChanged != null)
                OnNotifyCollectionChanged(action, new List<TItem>() { changedItem });
        }

        #region INotifyCollectionChanged Members

        [field: NonSerialized]
        public event NotifyCollectionChangedEventHandler CollectionChanged;

        #endregion

        #region ICollection Members

        public void CopyTo(Array array, int index)
        {
            _stack.CopyTo((TItem[])array, index);
        }

        public bool IsSynchronized
        {
            get { return true; }
        }

        public object SyncRoot
        {
            get { return this; }
        }

        #endregion

        #region IEnumerable Members

        public IEnumerator GetEnumerator()
        {
            return _stack.GetEnumerator();
        }

        #endregion
    }
}